package chapter_10;

// 异常处理机制
/*
10.1 异常处理基础

Java异常移除通过5个关键字控制：try、catch、throw、throws和finally。
异常处理块的通用形式：
try {
    // block of code to monitor for errors
}
catch (ExceptionType1 exOb) {
    // exception handler for ExceptionType1
}
catch (ExceptionType2 exOb) {
    // exception handler for ExceptionType2
}
// ...
finally {
    // block of code to be executed before try block ends
}
这里的ExceptionType是发生异常的类型。
 */


/*
10.2 异常类型
所有异常类型都是内置类Throwable的子类。因此，Throwable在异常类层次结构的顶层。紧接着Throwable
下面的是两个把异常分成两个不同分支的子类。一个分支是Exception，该类用于用户程序可能捕捉的异常情
况。在Exception分支中有一个非常重要的子类RuntimeException，该类型的异常自动为你所编写的程序
定义并且包括被零除和非法数组索引这样的错误。
另一类分支由Error作为顶层，Error定义了在通常环境下不希望被程序捕获的异常。Error类型的异常用于
Java运行时系统来显示与运行时系统本身有关的错误。堆栈溢出是这种错误的一例。Error类型的异常通常是
灾难性的致命错误，不是你的程序可以控制的。
 */


/*
10.3 未被捕获的异常
 */


/*
10.4 使用try和catch
10.4.1 显示一个异常的描述
Throwable重载toString()方法，所以它返回一个包含异常描述的字符串。你可以通过在println()中传给
异常一个参数来显示该异常的描述。
 */


/*
10.5 使用多重catch语句
当使用多catch语句时，记住异常子类必须在它们任何父类之前使用是很重要的。这是因为运用父类的catch
语句将捕获该类型及其子类类型的异常。这样，如果子类在父类后面，子类将永远不会到达。而且，Java中
不能到达的代码是一个错误。
 */


/*
10.6 嵌套try语句
try语句可以被嵌套。也就是说，一个try语句可以在另一个try块内部。每次进入try语句，异常的前后关系
都会被推入堆栈。如果一个内部的try语句不含特殊异常的catch处理程序，堆栈将弹出，下一个try语句的
catch处理程序将检查是否与之匹配。这个过程将继续直到一个catch语句匹配成功，或者是直到所有的嵌套
try语句被检查耗尽。如果没有catch语句匹配，Java的运行时系统将处理这个异常。
 */


/*
10.7 引发throw
throw语句的通常形式如下：
throw ThrowableInstance;
这里的ThrowableInstance一定是Throwable类类型或它的子类类型的一个对象。有两种可以获得Throwable
对象的方法：在catch子句中使用参数或者用new操作符创建。
程序执行在throw语句之后立即体质，后面的任何语句不被执行。最紧紧包围的try块用来检查它是否含有一个
与异常类型匹配的catch语句。如果发现了匹配的块，控制转向该语句；如果没有发现，次包围的try块来检查，
依此类推。如果没有发现匹配的catch块，默认异常处理程序中断程序的执行并且打印堆栈轨迹。
 */


/*
10.8 throws
如果一个方法可以导致一个异常但不处理它，它必须指定这种行为以使方法的调用者可以保护它们自己而不发生
异常。做到这点你可以在方法声明中包含一个throws子句。一个throws子句列举了一个方法可能引发的所有
的异常类型。这对于除Error或RuntimeException及它们子类以外类型的所有异常是必要的。一个方法可以
引发的所有其他异常必须在throws子句中声明。如果不这样做，将会导致编译错误。
下面是包含一个throws子句的方法声明的通用形式：
type method-name(parameter-list) throws exception-list {
    // body of method
}
这里，exception-list是该方法可以引发的以有逗号分隔的异常列表。

throw与throws的比较：
1. throws出现在方法函数头；而throw出现在函数体。
2. throws表示出现异常的一种可能性，并不一定会发生这些异常；throw则是抛出了异常，执行throw则一定
抛出了某种异常对象。
3. 两者都是消极处理异常的方式（这里的消极并不是说这种方式不好），只是抛出或者可能抛出异常，但是不
会由函数去处理异常，真正的处理异常由函数的上层调用处理。
 */


/*
10.9 finally
每一个try语句至少需要一个catch或finally子句。finally子句的代码不管怎么样都会执行的。
 */


/*
10.10 Java的内置异常
在标准包java.lang中，Java定义了若干个异常类。这些异常一般是标准类RuntimeException的子类。因为
java.lang实际上被所有的Java程序引入，多数从RuntimeException派生的异常都自动可用。而且，它们不
需要被包含在任何方法的throws列表中。Java语言中，这被叫做未经检查的异常。因为编译不检查它来看一个
方法是否处理或引发了这些异常。如果方法能产生的异常但是自己不能处理它，这些叫受检查的异常。

Java的java.lang中定义的未检查异常子类
异常                                  说明
ArithmeticException                 算术错误，如被0除
ArrayIndexOutOfBoundsException      数组下标出界
ArrayStoreException                 数组元素赋值类型不兼容
ClassCastException                  非法强制转换类型
IllegalArgumentException            调用方法的参数非法
IllegalMonitorStateException        非法监控操作，如等待一个未锁定线程
IllegalStateException               环境或应用状态不正确
IllegalThreadStateException         请求操作与当前线程状态不兼容
IndexOutOfBoundsException           某些类型索引越界
NullPointerException                非法使用空引用
NumberFormatException               字符串到数字格式非法转换
SecurityException                   试图违反安全性
StringIndexOutOfBounds              试图在字符串边界之外索引
UnsupportedOperationException       遇到不支持的操作

Java.lang中定义的检查异常
异常                                  说明
ClassNotFoundException              找不到类
CloneNotSupportedException          试图克隆一个不能实现Cloneable接口的对象
IllegalAccessException              对一个类的访问被拒绝
InstantiationException              试图创建一个抽象类或者抽象接口的对象
InterruptedException                一个线程被另一个线程中断
NoSuchFieldException                请求的字段不存在
NoSuchMethodException               请求的方法不存在
 */


/*
10.11 创建自己的异常子类
创建自己的异常类来处理一些特殊情况，这是非常简单的：只要定义Exception的一个子类就可以了
（Exception当然是Throwable的一个子类）。你的子类不需要实际执行什么——它们在类型系统中的
存在允许你把它们当成异常使用。
Exception类自己没有定义任何方法。当然，它继承了Throwable提供的一些方法。因此，所有异常，
包括你创建的，都可以获得Throwable定义的方法。我们还可以覆盖一个或多个Throwable的方法。
Throwable定义的方法
方法 描述
Throwable fillInStackTrace()    返回一个包含完整堆栈轨迹的Throwable对象，该对象可能
                                被再次引发
String getLocalizedMessage( )   返回一个异常的局部描述
String getMessage( )            返回一个异常的描述
void printStackTrace( )         显示堆栈轨迹
void printStackTrace(PrintStreamstream) 把堆栈轨迹送到指定的流
void printStackTrace(PrintWriterstream) 把堆栈轨迹送到指定的流
String toString( )              返回一个包含异常描述的String对象。当输出一个Throwable
                                对象时，该方法被println( )调用
 */


class MyException extends Exception {
    // 10.11
    private int detail;

    MyException(int a) {
        detail = a;
    }

    public String toString() {
        return "MyException[" + detail + "]";
    }
}


public class chapter_10 {

    static void subroutine() {
        // 10.3
        int d = 0;
        int a = 10 / d;
    }

    static void subroutine2() {
        // 10.4
        try {
            int d = 0;
            int a = 10 / d;
        } catch (ArithmeticException e) {
            System.out.println("Division by zero.");
            // 10.4.1
            System.out.println("Exception: " + e);
        }
        System.out.println("After catch statement.");
    }

    static void mCatch() {
        // 10.5
        int arr[] = {};
//        int arr[] = {1, 2, 3};
        try {
            int a = arr.length;
            System.out.println("a = " + a);
            int b = 42 / a;
            int c[] = { 1 };
            c[42] = 99;
        } catch (ArithmeticException e) {
            System.out.println("Divide by 0: " + e);
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("Array index oob: " + e);
        }
        System.out.println("After try/catch blocks.");
    }

    static void demoproc() {
        // 10.7
        try {
            // 所有的Java内置的运行时异常有两个构造函数：一个没有参数，一个带有一个字符串参数。
            // 当用到第二种形式时，参数指定描述异常的字符串。如果对象用作print()或println()的
            // 参数时，该字符串被显示。这同样可以通过调用getMessage()来实现，getMessage()是
            // 由Throwable定义的。
            throw new NullPointerException("demo");
        } catch (NullPointerException e) {
            System.out.println("Caught inside demoproc.");
            throw e;    // rethrow the exception
        }
    }

//    static void throwErrorDemo() {    // error
    static void throwsDemo() throws IllegalAccessException {
        // 10.8
        System.out.println("Inside throwOne.");
        throw new IllegalAccessException("demo");
    }

    static void procA() {
        // 10.9
        try {
            System.out.println("inside procA");
            throw new RuntimeException("demo");
        } finally {
            System.out.println("procA's finally");
        }
    }

    static int procB() {
        // 10.9
        try {
            System.out.println("inside procB");
            return 10;
        } finally {
            // 如果finally块与一个try联合使用，finally块将在try结束之前执行
            System.out.println("procB's finally");
            // 不要再finally语句中写return语句
            return 20;  // 实际上返回的是20
        }
    }

    static void procC() {
        try {
            System.out.println("inside procC");
        } finally {
            System.out.println("procC's finally");
        }
    }

    static void compute(int a) throws MyException {
        // 10.11
        System.out.println("Called compute(" + a + ")");
        if (a > 10)
            throw  new MyException(a);
        System.out.println("Normal exit");
    }

    public static void main(String[] args) {
//        int d = 0;
//        int a = 42 / d;

//        subroutine();
//        subroutine2();
//        mCatch();

//        try {
//            demoproc();
//        } catch (NullPointerException e) {
//            System.out.println("Recaught: " + e);
//        }

        // 错误调用
//        throwsErrorDemo();

//        try {
//            throwsDemo();
//        } catch (IllegalAccessException e) {
//            System.out.println("Caught " + e);
//        }

//        try {
//            procA();
//        } catch (Exception e) {
//            System.out.println("Exception caught");
//        }
        System.out.println(procB());
//        procC();

//        try {
//            compute(1);
//            compute(20);
//        } catch (MyException e) {
//            System.out.println("Caught " + e);
//        }
    }
}

